package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.cache.CategoryCache;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.ls.LSInput;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author mxl
 * @since 2024-06-30
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;

    private final CategoryCache categoryCache;

    private final IExchangeCodeService exchangeCodeService;

    private final IUserCouponService userCouponService;

    private final StringRedisTemplate redisTemplate;

    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        log.debug("保存优惠券信息");
        // 1. dto转po 保存优惠券信息 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        // 2. 判断是否限定了范围，dto.specific 如果为false直接return
        if(!dto.getSpecific()){
            return ;
        }
        // 3. 如果dto.specific 为true 需要校验dtp.scope
        if(CollUtils.isEmpty(dto.getScopes())){
            throw new BadRequestException("分类id不能为空");
        }
        // 3. 保存优惠前信息
        List<CouponScope> couponScopes = new ArrayList<>();
        for (Long scope : dto.getScopes()) {
            CouponScope couponScope = new CouponScope();
            couponScope.setCouponId(coupon.getId());
            couponScope.setBizId(scope);
            couponScopes.add(couponScope);
        }
        // 4. 保存优惠券的限定范围， coupon_scope 批量新增
        couponScopeService.saveBatch(couponScopes);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery couponQuery) {
        // 查询db获取数据库列表
        Page<Coupon> page = this.lambdaQuery()
                .eq(couponQuery.getType() != null, Coupon::getDiscountType, couponQuery.getType())
                .eq(couponQuery.getStatus() != null, Coupon::getStatus, couponQuery.getStatus())
                .like(StringUtils.isNotBlank(couponQuery.getName()), Coupon::getName, couponQuery.getName())
                .page(couponQuery.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        // 封装vo
        List<CouponPageVO> couponPageVOS = BeanUtils.copyList(records, CouponPageVO.class);
        return PageDTO.of(page, couponPageVOS);
    }

    @Override
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        // 1. 校验参数
        if(id == null || !id.equals(dto.getId())){
            throw new BadRequestException("请求参数错误");
        }
        // 2. 校验id是否存在
        Coupon coupon = this.getById(id);
        if(coupon == null){
            throw new BadRequestException("优惠券不存在");
        }

        // 3. 校验优惠券状态、只有待发放状态才能发放
        if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE){
            throw new BadRequestException("只有待发放和暂停状态的优惠券才能发放");
        }

        // 是否立刻发放
        boolean isBeginIssue = dto.getIssueBeginTime() == null || !dto.getIssueBeginTime().isAfter(LocalDateTime.now());
        // 4. 修改优惠券的 领取开始时间和结束时间  使用有效期开始和结束日  天数 状态
        Coupon tmp = BeanUtils.copyBean(dto, Coupon.class);
        if(isBeginIssue){
            tmp.setStatus(CouponStatus.ISSUING);
            tmp.setIssueBeginTime(LocalDateTime.now());
        }else{
            tmp.setStatus(CouponStatus.UN_ISSUE);
        }
        this.updateById(tmp);

        // 5. 如果优惠券是立刻发放，将优惠券信息（优惠券id， 领券开始时间、发放总数、已领数量） 采用Hash 存入redis
        if(isBeginIssue){
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX+id;
            redisTemplate.opsForHash().put(key, "issueBeginTime", String.valueOf(DateUtils.toEpochMilli(LocalDateTime.now())));
            redisTemplate.opsForHash().put(key, "issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            redisTemplate.opsForHash().put(key, "totalNum", String.valueOf(coupon.getTotalNum()));
            redisTemplate.opsForHash().put(key, "userLimit", String.valueOf(coupon.getUserLimit()));
        }

        // 6. 如果方式是指定发放需要生成兑换码  (状态需要为待发放状态)
        if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
            coupon.setIssueEndTime(tmp.getIssueEndTime()); // 兑换码兑换的戒指时间，就是优惠券领取的截至时间
            exchangeCodeService.asyncGenerateExchangeCode(coupon);
        }

    }

    @Override
    @Transactional
    public void updateCoupon(Long id, CouponFormDTO dto) {
        // 1. 校验参数
        if(id == null || dto == null || !id.equals(dto.getId())){
            throw new BadRequestException("请求参数不能为空");
        }

        // 2. 查db
        Coupon coupon = this.getById(id);

        // 3. 判断是否存在和状态是否正确 (只有待发发送才能修改)
        if(coupon == null || coupon.getStatus() != CouponStatus.DRAFT){
            throw new BadRequestException("优惠券不存在或优惠券状态不为待发放");
        }
        // 4. 更新优惠券信息
        Coupon couponDB = BeanUtils.copyBean(dto, Coupon.class);
        this.save(couponDB);
        // 5. 处理范围信息
        // 5.1 判断现有状态和之前的状态
        // 都不限定范围
        if(!coupon.getSpecific() && !couponDB.getSpecific()) return;

        Set<Long> originScopes;
        if(coupon.getSpecific()){
            List<CouponScope> list = couponScopeService.lambdaQuery()
                    .eq(CouponScope::getCouponId, id).list();
            originScopes = list.stream().map(couponScope -> couponScope.getBizId()).collect(Collectors.toSet());
        }else{
            originScopes = new HashSet<>();
        }

        Set<Long> newScopes;
        if(couponDB.getSpecific() && !CollUtils.isEmpty(dto.getScopes())){
            newScopes = new HashSet<>(dto.getScopes());
        }else{
            newScopes = new HashSet<>();
        }
        // 不包含在新的范围内的，需要删除的类别
        List<Long> needRemove = originScopes.stream().filter(scope -> !newScopes.contains(scope)).collect(Collectors.toList());
        // 新类别中需要增加的数据
        List<Long> needAdd = newScopes.stream().filter(scope -> !originScopes.contains(scope)).collect(Collectors.toList());

        if(!CollUtils.isEmpty(needRemove)){
            couponScopeService.remove(Wrappers.<CouponScope>lambdaQuery()
                    .eq(CouponScope::getCouponId, id)
                    .in(CouponScope::getBizId, needRemove));
        }

        if(!CollUtils.isEmpty(needAdd)){
            List<CouponScope> couponScopes = needAdd.stream()
                    .map(bizId -> new CouponScope().setCouponId(id).setBizId(bizId))
                    .collect(Collectors.toList());
            couponScopeService.saveBatch(couponScopes);
        }
    }

    @Override
    public void deleteCoupon(Long id) {
        // 1. 校验参数
        if(id == null) {
            throw new BadRequestException("参数为空");
        }
        // 2. 校验状态
        Coupon coupon = this.getById(id);
        if(coupon == null || coupon.getStatus() != CouponStatus.DRAFT){
            throw new BadRequestException("优惠券不存在或正在使用中");
        }
        // 3. 删除
        // 3.1 删除优惠券
        boolean isRemove = this.remove(Wrappers.<Coupon>lambdaQuery()
                .eq(Coupon::getId, id)
                .eq(Coupon::getStatus, CouponStatus.DRAFT));
        if(!isRemove){
            throw new BadRequestException("优惠券不存在或正在使用中");
        }
        // 3.2 删除优惠券作用范围
        if(!coupon.getSpecific()) return;

        couponScopeService.remove(Wrappers.<CouponScope>lambdaQuery()
                .eq(CouponScope::getCouponId, id));
    }

    @Override
    public CouponDetailVO queryCoupon(Long id) {
        // 1. 参数校验
        if(id == null){
            throw new BadRequestException("参数为空");
        }

        // 2. 查询优惠券信息
        Coupon coupon = this.getById(id);
        if(coupon == null){
            throw new BadRequestException("优惠券信息不存在");
        }
        // 3. 查询限定范围信息
        List<CouponScope> couponScopes = null;
        if(coupon.getSpecific()){
             couponScopes = couponScopeService.lambdaQuery()
                    .eq(CouponScope::getCouponId, id).list();
        }
        // 4. 封装vo
        CouponDetailVO couponDetailVO = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        if(CollUtils.isEmpty(couponScopes)){
            return couponDetailVO;
        }
        List<CouponScopeVO> scopeVOS = couponScopes.stream()
                .map(CouponScope::getBizId)
                .map(cateId -> new CouponScopeVO(cateId, categoryCache.getNameByLv3Id(cateId)))
                .collect(Collectors.toList());
        couponDetailVO.setScopes(scopeVOS);
        return couponDetailVO;
    }

    @Override
    public void beginIssueBatch(List<Coupon> coupons) {
        // 1.更新券状态
        for (Coupon c : coupons) {
            c.setStatus(CouponStatus.ISSUING);
        }
        updateBatchById(coupons);
    }

    @Override
    public void EndIssueBatch(List<Coupon> records) {
        // 1.更新券状态
        for (Coupon c : records) {
            c.setStatus(CouponStatus.FINISHED);
        }
        updateBatchById(records);
    }

    // 查询发放中的优惠券
    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1. 查询db coupon 条件：发放中  手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();

        if(CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }
        // 2. 查询用户卷表
        List<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toList());
        // 2.1 获取当前用户
        Long user = UserContext.getUser();
        // 2.2 当前用户每个卷的领取数量
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, user)
                .in(UserCoupon::getCouponId, couponIds)
                .list();

        Map<Long, Long> issueMap = userCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        // 2.3 当前用户未使用的券
        Map<Long, Long> unuseMap = userCoupons.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));


        List<CouponVO> voList = new ArrayList<>();
        // 2. po 转 vo
        for (Coupon c: couponList){
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);
            Long issuNume = issueMap.getOrDefault(c.getId(),0L);
            // 当前优惠券未发完且用户领取优惠券未超过限制
            boolean isAvailable = c.getIssueNum() < c.getTotalNum() && issuNume < c.getUserLimit();
            vo.setAvailable(isAvailable);  // 是否可以领取     已领数量与限领数量
            // 用户已领但没有使用的数据量
            boolean isReceived = unuseMap.getOrDefault(c.getId(),0L) > 0;
            vo.setReceived(isReceived);   // 是否可以使用
            voList.add(vo);
        }
        return voList;
    }

    @Override
    @Transactional
    public void pauseIssue(Long id) {
        // 1.查询旧优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        // 2.当前券状态必须是未开始或进行中
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.ISSUING.UN_ISSUE && status != CouponStatus.ISSUING) {
            // 状态错误，直接结束
            return;
        }

        // 3.更新状态
        boolean success = lambdaUpdate()
                .set(Coupon::getStatus, CouponStatus.PAUSE)
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, CouponStatus.UN_ISSUE, CouponStatus.ISSUING)
                .update();
        if (!success) {
            // 可能是重复更新，结束
            log.error("重复暂停优惠券");
        }

    }

}
